package org.proteored.miapeapi.xml.pride.adapter;

import java.io.IOException;
import java.math.BigInteger;
import java.net.URL;
import java.util.HashMap;
import java.util.List;
import java.util.Set;

import org.apache.log4j.Logger;
import org.proteored.miapeapi.cv.Accession;
import org.proteored.miapeapi.cv.ControlVocabularyManager;
import org.proteored.miapeapi.cv.ControlVocabularyTerm;
import org.proteored.miapeapi.cv.PSIModOntology;
import org.proteored.miapeapi.cv.UNIMODOntology;
import org.proteored.miapeapi.cv.ms.ChargeState;
import org.proteored.miapeapi.cv.ms.MOverZ;
import org.proteored.miapeapi.cv.ms.RetentionTime;
import org.proteored.miapeapi.cv.msi.PeptideModificationName;
import org.proteored.miapeapi.cv.msi.Score;
import org.proteored.miapeapi.exceptions.IllegalMiapeArgumentException;
import org.proteored.miapeapi.experiment.model.ExtendedIdentifiedPeptide;
import org.proteored.miapeapi.interfaces.Adapter;
import org.proteored.miapeapi.interfaces.msi.PeptideModification;
import org.proteored.miapeapi.interfaces.msi.PeptideScore;
import org.proteored.miapeapi.xml.pride.autogenerated.CvParamType;
import org.proteored.miapeapi.xml.pride.autogenerated.ExperimentType.MzData.SpectrumList.Spectrum;
import org.proteored.miapeapi.xml.pride.autogenerated.Modification;
import org.proteored.miapeapi.xml.pride.autogenerated.ObjectFactory;
import org.proteored.miapeapi.xml.pride.autogenerated.ParamType;
import org.proteored.miapeapi.xml.pride.autogenerated.Peptide;
import org.proteored.miapeapi.xml.pride.autogenerated.PrecursorType;
import org.proteored.miapeapi.xml.pride.autogenerated.SpectrumDescType;
import org.proteored.miapeapi.xml.pride.autogenerated.SpectrumDescType.PrecursorList;
import org.proteored.miapeapi.xml.pride.util.PrideControlVocabularyXmlFactory;
import org.springframework.core.io.ClassPathResource;

import uk.ac.ebi.pridemod.PrideModController;
import uk.ac.ebi.pridemod.slimmod.model.SlimModCollection;
import uk.ac.ebi.pridemod.slimmod.model.SlimModification;

public class PeptideAdapter implements Adapter<Peptide> {
	private static Logger log = Logger.getLogger("log4j.logger.org.proteored");
	ClassPathResource resource = new ClassPathResource(
			"modification_mappings.xml");
	private final ControlVocabularyManager cvManager;
	private final ObjectFactory factory;
	private final ExtendedIdentifiedPeptide identifiedPeptide;
	private final PrideControlVocabularyXmlFactory prideCvUtil;
	private static SlimModCollection preferredModifications;
	private final ControlVocabularyTerm chargeStateTerm;
	private final ControlVocabularyTerm mOverZTerm;
	private final ControlVocabularyTerm selectedMOverZTerm;
	private final double tolerance = 0.01; // Daltons of tolerance used to match
											// experimental m/z from mzid to
											// model
	private BigInteger spectrumReference;
	private final boolean includeSpectra;

	// to the correct spectrum

	public PeptideAdapter(ObjectFactory factory,
			ControlVocabularyManager cvManager,
			ExtendedIdentifiedPeptide identifiedPeptide, boolean includeSpectra) {
		this.cvManager = cvManager;
		this.factory = factory;
		this.identifiedPeptide = identifiedPeptide;
		prideCvUtil = new PrideControlVocabularyXmlFactory(factory, cvManager);
		chargeStateTerm = ChargeState.getInstance(cvManager)
				.getChargeStateTerm();
		mOverZTerm = MOverZ.getMOverZTerm(cvManager);
		selectedMOverZTerm = MOverZ.getSelected_Ion_MOverZTerm(cvManager);
		this.includeSpectra = includeSpectra;
	}

	@Override
	public Peptide adapt() {
		Peptide xmlPeptide = factory.createPeptide();
		xmlPeptide.setSequence(identifiedPeptide.getSequence());
		if (identifiedPeptide.getRetentionTimeInSeconds() != null) {
			try {
				double rtInSeconds = Double.valueOf(identifiedPeptide
						.getRetentionTimeInSeconds());
				double rtInMinutes = rtInSeconds / 60;
				ControlVocabularyTerm rtTerm = RetentionTime.getInstance(
						cvManager).getCVTermByAccession(
						new Accession(RetentionTime.RetentionTimeMinutes));
				prideCvUtil.addCvParamToParamType(xmlPeptide.getAdditional(),
						rtTerm.getTermAccession(), rtTerm.getPreferredName(),
						String.valueOf(rtInMinutes), rtTerm.getCVRef());
			} catch (NumberFormatException e) {

			}
		}

		// get the offset of the spectrum reference
		if (identifiedPeptide.getSpectrumRef() != null) {
			if (identifiedPeptide.getMiapeMSReference() != null
					&& identifiedPeptide.getMiapeMSReference() != -1) {
				spectrumReference = SpectrumListAdapter
						.getPeptideSpectrumReference(
								identifiedPeptide.getMiapeMSReference(),
								identifiedPeptide.getSpectrumRef());
				xmlPeptide.setSpectrumReference(spectrumReference);
			} else {
				try {
					spectrumReference = BigInteger.valueOf(Long
							.valueOf(identifiedPeptide.getSpectrumRef()));
					xmlPeptide.setSpectrumReference(spectrumReference);
				} catch (NumberFormatException e) {
					if (includeSpectra)
						throw e;
				}
			}
		}
		// throw exception if the spectra whould be referenced and it is not
		// found
		if (includeSpectra
				&& (spectrumReference == null || spectrumReference.toString()
						.equals("-1")))
			throw new IllegalMiapeArgumentException(
					"Referenced Spectrum not found");

		final String charge = identifiedPeptide.getCharge();
		if (charge != null && xmlPeptide.getSpectrumReference() != null) {
			recalibrateChargeAndPrecursorMass(xmlPeptide, charge);

		}
		// try {
		// final String spectrumRef = identifiedPeptide.getSpectrumRef();
		// final long longValue = Long.valueOf(spectrumRef) + offset;
		// final BigInteger bigIntValue = BigInteger.valueOf(longValue);
		// xmlPeptide.setSpectrumReference(bigIntValue);
		// } catch (Exception e) {
		// e.printStackTrace();
		// log.warn(e.getMessage());
		// }

		// scores
		if (xmlPeptide.getAdditional() == null)
			xmlPeptide.setAdditional(factory.createParamType());
		Set<PeptideScore> scores = identifiedPeptide.getScores();
		if (scores != null) {
			for (PeptideScore peptideScore : scores) {
				ControlVocabularyTerm scoreTerm = Score.getInstance(cvManager)
						.getCVTermByPreferredName(peptideScore.getName());
				if (scoreTerm != null) {
					prideCvUtil.addCvParamToParamType(
							xmlPeptide.getAdditional(),
							scoreTerm.getTermAccession(),
							peptideScore.getName(), peptideScore.getValue(),
							scoreTerm.getCVRef());
				} else {
					prideCvUtil.addUserParamToParamType(
							xmlPeptide.getAdditional(), peptideScore.getName(),
							peptideScore.getValue());
				}

			}
		}
		// modifications
		Set<PeptideModification> modifications = identifiedPeptide
				.getModifications();
		if (modifications != null) {
			for (PeptideModification peptideModification : modifications) {
				xmlPeptide.getModificationItem().add(
						getModification(peptideModification));
			}
		}

		// Local FDR

		Float localFDR = identifiedPeptide.getPeptideLocalFDR();
		if (localFDR != null) {
			ControlVocabularyTerm localFDRTerm = Score
					.getLocalFDRTerm(cvManager);
			if (localFDRTerm != null) {
				prideCvUtil.addCvParamToParamType(xmlPeptide.getAdditional(),
						localFDRTerm.getTermAccession(),
						localFDRTerm.getPreferredName(), localFDR.toString(),
						localFDRTerm.getCVRef());
			}
		}
		return xmlPeptide;
	}

	/**
	 * Try to change the charge state if different in the PSM from the spectrum
	 * annotation
	 * 
	 * @param xmlPeptide
	 * @param charge
	 */
	private void recalibrateChargeAndPrecursorMass(Peptide xmlPeptide,
			String charge) {

		if (identifiedPeptide.getExperimentalMassToCharge() != null) {
			double moverz = Double.valueOf(identifiedPeptide
					.getExperimentalMassToCharge());
			boolean spectrumMatched = false;
			final HashMap<Double, Spectrum> spectrumBymoverZMap = SpectrumListAdapter
					.getSpectrumByMoverZMap();
			if (spectrumBymoverZMap.containsKey(moverz)) {
				spectrumReference = new BigInteger(
						String.valueOf(spectrumBymoverZMap.get(moverz).getId()));
				xmlPeptide.setSpectrumReference(spectrumReference);
				spectrumMatched = true;
			}

			// try to change the charge in the corresponding spectrum
			final HashMap<BigInteger, Spectrum> spectrumMap = SpectrumListAdapter
					.getSpectrumMap();

			if (spectrumMap != null
					&& spectrumMap.containsKey(spectrumReference)) {
				Spectrum spectrum = spectrumMap.get(spectrumReference);
				List<Object> precursorCVParams = getPrecursorCVParams(spectrum);
				if (precursorCVParams != null) {
					try {
						// look at the spectrum and take the one
						// that has that m over z

						// look at the referenced spectrum
						for (Object object : precursorCVParams) {
							if (object instanceof CvParamType) {
								CvParamType cvParam = (CvParamType) object;
								if (cvParam.getAccession().equals(
										mOverZTerm.getTermAccession()
												.toString())
										|| cvParam.getAccession().equals(
												selectedMOverZTerm
														.getTermAccession()
														.toString())) {

									if (Math.abs(moverz
											- Double.valueOf(cvParam.getValue())) < tolerance) {
										spectrumMatched = true;
										break;
									}
								}
							}
						}

						// if the referenced spectrum has no the
						// experimental m/z as the peptide, search
						// in the following spectrums
						if (!spectrumMatched) {
							BigInteger previousSpectrumReference = spectrumReference;
							// start on 5 spectrum before
							spectrumReference = spectrumReference
									.subtract(BigInteger.valueOf(100));
							log.debug("Referenced spectrum (ID:"
									+ previousSpectrumReference
									+ ") doesn't match with the m/z value: "
									+ moverz
									+ ". Looking other spectra starting by "
									+ spectrumReference + " ...");
							int maxNumIterations = 400;
							int numIteration = 0;
							while (!spectrumMatched) {
								// sum 1 to the reference
								spectrumReference = spectrumReference
										.add(BigInteger.valueOf(1));
								numIteration++;
								if (numIteration == maxNumIterations) {
									throw new IllegalMiapeArgumentException(
											"Spectrum referenced as: "
													+ previousSpectrumReference
													+ " not found in the spectrum list with m/z="
													+ moverz);
								}
								log.debug("Looking at spectrum ID:"
										+ spectrumReference);
								spectrum = spectrumMap.get(spectrumReference);
								if (spectrum != null) {
									precursorCVParams = getPrecursorCVParams(spectrum);
									for (Object object : precursorCVParams) {
										if (object instanceof CvParamType) {
											CvParamType cvParam = (CvParamType) object;
											if (cvParam.getAccession().equals(
													mOverZTerm
															.getTermAccession()
															.toString())
													|| cvParam
															.getAccession()
															.equals(selectedMOverZTerm
																	.getTermAccession()
																	.toString())) {
												if (Math.abs(moverz
														- Double.valueOf(cvParam
																.getValue())) < tolerance) {
													log.debug("Spectrum found with ID:"
															+ spectrumReference
															+ " spectrum m/z="
															+ cvParam
																	.getValue()
															+ " -> peptide m/z="
															+ moverz);
													log.info("Referenced spectrum changed from "
															+ previousSpectrumReference
															+ " to "
															+ spectrumReference
															+ " ("
															+ moverz
															+ "-"
															+ cvParam
																	.getValue()
															+ ")");
													spectrumMatched = true;

													xmlPeptide
															.setSpectrumReference(spectrumReference);
													break;
												}
											}
										}
									}
								}
							}
						}
						precursorCVParams = getPrecursorCVParams(spectrum);
						for (Object object : precursorCVParams) {
							if (object instanceof CvParamType) {
								CvParamType cvParam = (CvParamType) object;
								if (cvParam.getAccession().equals(
										chargeStateTerm.getTermAccession()
												.toString())) {
									if (!cvParam.getValue().equals(charge)) {
										log.info("Changing charge of peptide "
												+ identifiedPeptide
														.getSequence()
												+ " and spectrum ID:"
												+ spectrumReference + " from "
												+ cvParam.getValue() + " to "
												+ charge);
										cvParam.setValue(charge);

									}
								}
							}

						}
					} catch (Exception e) {

					}

				} else {
					log.warn("Spectrum " + spectrumReference + " not found");
				}
			}
		}
	}

	private List<Object> getPrecursorCVParams(Spectrum spectrum) {
		if (spectrum != null) {
			final SpectrumDescType spectrumDesc = spectrum.getSpectrumDesc();
			if (spectrumDesc != null) {
				final PrecursorList precursorList = spectrumDesc
						.getPrecursorList();
				if (precursorList != null
						&& precursorList.getPrecursor() != null) {
					for (PrecursorType precursor : precursorList.getPrecursor()) {
						final ParamType ionSelection = precursor
								.getIonSelection();
						if (ionSelection != null) {
							return ionSelection.getCvParamOrUserParam();
						}
					}
				}
			}
		}
		return null;
	}

	// private int getPeptideOffset() {
	// HashMap<Integer, Integer> miapeIDSpectraOffsets = SpectrumListAdapter
	// .getMiapeIDSpectraOffsets();
	// Integer miapeMSReference = this.identifiedPeptide.getMiapeMSReference();
	// if (miapeMSReference != null &&
	// miapeIDSpectraOffsets.containsKey(miapeMSReference)) {
	// return miapeIDSpectraOffsets.get(miapeMSReference);
	// } else {
	// return 0;
	// }
	// }

	private Modification getModification(PeptideModification peptideModification) {
		Modification xmlModification = factory.createModification();
		// mandatory fields
		xmlModification.setModDatabase("");
		xmlModification.setModAccession("");

		if (peptideModification == null)
			return xmlModification;
		String replacementResidue = peptideModification.getReplacementResidue();
		String name = peptideModification.getName();
		if (name != null) {
			SlimModCollection preferredModifications = getPreferredModifications();

			if (preferredModifications != null) {
				SlimModification mod = preferredModifications
						.getbyName(peptideModification.getName());
				if (mod != null) {
					Modification xmlModification2 = getModificationFromMapping(
							peptideModification, mod);
					if (xmlModification2 != null)
						return xmlModification2;
				}
				if (peptideModification.getMonoDelta() != null) {
					SlimModCollection mods = preferredModifications.getbyDelta(
							peptideModification.getMonoDelta(), 0.01);
					if (!mods.isEmpty()) {
						mod = mods.get(0);
						Modification xmlModification2 = getModificationFromMapping(
								peptideModification, mod);
						if (xmlModification2 != null)
							return xmlModification2;
					}
				}
				if (peptideModification.getAvgDelta() != null) {
					SlimModCollection mods = preferredModifications.getbyDelta(
							peptideModification.getAvgDelta(), 0.01);
					if (!mods.isEmpty()) {
						mod = mods.get(0);
						Modification xmlModification2 = getModificationFromMapping(
								peptideModification, mod);
						if (xmlModification2 != null)
							return xmlModification2;
					}
				}
			}

			// the ModAccession is actually the name of the modification
			xmlModification.setModAccession(name);
			xmlModification.setAdditional(factory.createParamType());
			// if it is a CVTerm, add the cvParam in the Additional element.
			// if not, add the userParam in the additional element.
			final ControlVocabularyTerm cvTerm = PeptideModificationName
					.getInstance(cvManager).getCVTermByPreferredName(name);
			// final Accession controlVocabularyId =
			// cvManager.getControlVocabularyId(name,
			// PeptideModificationName.getInstance(cvManager));

			if (cvTerm != null) {
				xmlModification.setModDatabase(cvTerm.getCVRef());
				// add the cvTerm to the Additional element
				prideCvUtil.addCvParamToParamType(
						xmlModification.getAdditional(),
						cvTerm.getTermAccession(), cvTerm.getPreferredName(),
						null, cvTerm.getCVRef());
				// if the mod is a cvTerm, substitute the accession:
				xmlModification.setModAccession(cvTerm.getTermAccession()
						.toString());

			} else { // it is not a CVTerm
				if (replacementResidue == null) {
					xmlModification.setModDatabase(UNIMODOntology.getCVLabel());
					// add the cvTerm to the Additional element
					prideCvUtil.addUserParamToParamType(
							xmlModification.getAdditional(),
							UNIMODOntology.getCVLabel(), name);
				} else {
					xmlModification.setModDatabase("None");
				}
			}
		}

		if (peptideModification.getPosition() > 0)
			xmlModification.setModLocation(new BigInteger(Integer.valueOf(
					peptideModification.getPosition()).toString()));

		Double monoDelta = peptideModification.getMonoDelta();
		if (monoDelta != null)
			xmlModification.getModMonoDelta().add(monoDelta.toString());

		Double avgDelta = peptideModification.getAvgDelta();
		if (avgDelta != null)
			xmlModification.getModAvgDelta().add(avgDelta.toString());

		Double neutralLoss = peptideModification.getNeutralLoss();
		if (neutralLoss != null) {
			xmlModification
					.setAdditional(prideCvUtil.addCvParamOrUserParamToParamType(
							xmlModification.getAdditional(),
							PeptideModificationName
									.getInstance(cvManager)
									.getCVTermByAccession(
											PeptideModificationName.FRAGMENT_NEUTRAL_LOSS_ACCESSION)
									.getPreferredName(),
							neutralLoss.toString(),
							PeptideModificationName.getInstance(cvManager)));
		}

		if (replacementResidue != null) {
			prideCvUtil.addUserParamToParamType(
					xmlModification.getAdditional(),
					PrideControlVocabularyXmlFactory.REPLACEMENT_RESIDUE,
					peptideModification.getResidues() + " by "
							+ peptideModification.getReplacementResidue());
		}
		if (peptideModification.getModificationEvidence() != null
				&& !"".equals(peptideModification.getModificationEvidence())) {
			prideCvUtil.addUserParamToParamType(
					xmlModification.getAdditional(), "Modification evidence",
					peptideModification.getModificationEvidence());
		}
		return xmlModification;
	}

	private SlimModCollection getPreferredModifications() {

		if (PeptideAdapter.preferredModifications == null) {
			URL url;
			try {
				url = resource.getURL();
				PeptideAdapter.preferredModifications = PrideModController
						.parseSlimModCollection(url);
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
		return PeptideAdapter.preferredModifications;
	}

	private Modification getModificationFromMapping(
			PeptideModification peptideModification, SlimModification mod) {
		Modification xmlModification = factory.createModification();
		final String idPsiMod = mod.getIdPsiMod();
		String replacementResidue = peptideModification.getReplacementResidue();
		final ControlVocabularyTerm cvTermByAccession = PeptideModificationName
				.getInstance(cvManager).getCVTermByAccession(
						new Accession(idPsiMod));
		if (cvTermByAccession != null) {
			// ACCESSION
			xmlModification.setModAccession(idPsiMod);
			// DATABASE
			xmlModification.setModDatabase(PSIModOntology.getCVLabel());
			// DATABASE VERSION
			xmlModification.setModDatabaseVersion(PSIModOntology.getVersion());
			// POSITION
			if (peptideModification.getPosition() > -1)
				xmlModification.setModLocation(new BigInteger(Integer.valueOf(
						peptideModification.getPosition()).toString()));

			xmlModification.setAdditional(factory.createParamType());
			// cvParam of the modification in PSI-MOD
			prideCvUtil.addCvParamToParamType(xmlModification.getAdditional(),
					cvTermByAccession.getTermAccession(),
					cvTermByAccession.getPreferredName(), null,
					cvTermByAccession.getCVRef());
			// MONO DELTA
			Double monoDelta = peptideModification.getMonoDelta();
			if (monoDelta != null)
				xmlModification.getModMonoDelta().add(monoDelta.toString());
			// AVG DELTA
			Double avgDelta = peptideModification.getAvgDelta();
			if (avgDelta != null)
				xmlModification.getModAvgDelta().add(avgDelta.toString());
			// NEUTRAL LOSS
			Double neutralLoss = peptideModification.getNeutralLoss();
			if (neutralLoss != null) {
				prideCvUtil
						.addCvParamOrUserParamToParamType(
								xmlModification.getAdditional(),
								PeptideModificationName
										.getInstance(cvManager)
										.getCVTermByAccession(
												PeptideModificationName.FRAGMENT_NEUTRAL_LOSS_ACCESSION)
										.getPreferredName(),
								neutralLoss.toString(),
								PeptideModificationName.getInstance(cvManager));
			}

			if (replacementResidue != null) {
				prideCvUtil.addUserParamToParamType(
						xmlModification.getAdditional(),
						PrideControlVocabularyXmlFactory.REPLACEMENT_RESIDUE,
						peptideModification.getResidues() + " by "
								+ peptideModification.getReplacementResidue());
			}
			return xmlModification;
		}
		return null;
	}
}
